/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hhf.flex.ascreator;

import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import hhf.flex.annotations.services.AddEntities;
import hhf.flex.annotations.services.ArgumentName;
import hhf.flex.annotations.services.CanceledService;
import hhf.flex.annotations.services.ChainLoad;
import hhf.flex.annotations.services.LinkedServices;
import hhf.flex.annotations.services.RemoveEntities;
import hhf.flex.annotations.services.ShowBusyCursor;
import hhf.flex.ascreator.exceptions.IgnoredClassException;
import hhf.flex.ascreator.exceptions.IllegalAnnotationException;
import hhf.flex.ascreator.exceptions.IllegalClassException;
import hhf.flex.ascreator.exceptions.IllegalParameterException;
import hhf.flex.ascreator.exceptions.MissingAnnotationException;
import hhf.flex.ascreator.exceptions.NoAnnotationClassException;
import hhf.flex.ascreator.exceptions.NoCommentClassException;
import hhf.flex.ascreator.exceptions.NoSuperClassException;
import hhf.flex.ascreator.patterns.ASAddEntitiesPattern;
import hhf.flex.ascreator.patterns.ASCancelFunctionPattern;
import hhf.flex.ascreator.patterns.ASChainLoadsPattern;
import hhf.flex.ascreator.patterns.ASDelayedFunctionPattern;
import hhf.flex.ascreator.patterns.ASPattern;
import hhf.flex.ascreator.patterns.ASRemoveEntitiesPattern;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author HHFrancois
 */
public abstract class ASAbstractServiceCreator extends ASCreator implements ASServiceCreatorInterface {
	//	hérité
	//	protected String path;
	//	protected Class _class;

	protected String id;
	protected Method method;
	protected TypeDeclaration typeDeclaration;
	protected String comment = "";
	// liste des Type d'argments AS
	protected List<String> args;
	// liste des nom des arguments
	protected List<String> argNames;
	// type du retour de la methode
	protected String returnType;
	// Class du retour de la method;
	protected Class classReturnType;
	// string de tous les noms arguments , suivi de 2 points Type séparés par des virgules(toto:String, tata:Object)
	protected String argsTyped = "";
	// string de tous les noms arguments séparés par des virgules (toto, tata, tutu)
	protected String arguments = "";
	// noms de tous les fonctions "cancel"
	protected List<String> cancelFunctionsNames;
	// noms de tous les fonctions "chainload"
	protected List<String> chainLoadFunctionsNames;
	//correspondance entre @ArgumentName et argNames
	protected Map<String, String> annotedNames;
	//correspondance entre @ArgumentName et Class
	protected Map<String, Class> annotedClass;
	protected boolean writeRemoveEntitiesFunction;
	protected boolean writeAddEntitiesFunction;
	protected String className;
	protected Set<String> imports;

	public ASAbstractServiceCreator() {
		super(null, null);
	}

	
	@Override
	public void init(final String path, final Method method, final String id, final Map<String, Object> options) throws IllegalAnnotationException {
		this.path = path;
		this.options = options;
		this.legacyCollection = (Boolean) options.get("legacy-collection");
		this.typeDeclaration = (TypeDeclaration) options.get("typeDeclaration");
		this.method = method;
		this.annotedNames = new HashMap<String, String>();
		this.annotedClass = new HashMap<String, Class>();
		this.id = id;
		this.classReturnType = method.getReturnType();

		Collection<ParameterDeclaration> paramDeclarations = null;
		// Retrouve la methode dans la déclaration retournée par APT
		// TODO à améliorer au niveau de la signature de la methode, et pour le commentaire, regarder les interfaces

		MethodDeclaration methodDeclaration = null;

		for (MethodDeclaration m : typeDeclaration.getMethods()) {
			if (m.getSimpleName().equals(method.getName())) {
				comment = m.getDocComment();
				methodDeclaration = m;
				paramDeclarations = m.getParameters();
				break;
			}
		}
		// Recupere les types concernés

		returnType = ASCreatorTools.getASType(method.getReturnType(), legacyCollection);
		args = ASCreatorTools.getASArguments(method, legacyCollection); // ordonnés
		argNames = new ArrayList<String>();
		cancelFunctionsNames = new ArrayList<String>();
		chainLoadFunctionsNames = new ArrayList<String>();
		// Récupere le nom des arguments
		if (paramDeclarations != null) {
			int i = 0;
			for (ParameterDeclaration paramDeclaration : paramDeclarations) {
				argNames.add(paramDeclaration.getSimpleName());
				// method.getParameterTypes()
				if (method.getParameterAnnotations() != null) {
					if (method.getParameterAnnotations()[i] != null) {
						for (Annotation a : method.getParameterAnnotations()[i]) {
							if (a instanceof ArgumentName) {
								annotedNames.put(((ArgumentName) a).value(), paramDeclaration.getSimpleName());
								annotedClass.put(((ArgumentName) a).value(), method.getParameterTypes()[i]);
							}
						}
					}
				}
				i++;
			}
		} else {
			for (int i = 0; i < args.size(); i++) {
				argNames.add("arg" + i);
				if (method.getParameterAnnotations() != null) {
					if (method.getParameterAnnotations()[i] != null) {
						for (Annotation a : method.getParameterAnnotations()[i]) {
							if (a instanceof ArgumentName) {
								annotedNames.put(((ArgumentName) a).value(), "arg" + i);
								annotedClass.put(((ArgumentName) a).value(), method.getParameterTypes()[i]);
							}
						}
					}
				}
			}

		}
		for (int i = 0; i < argNames.size();) {
			arguments += argNames.get(i);
			argsTyped += argNames.get(i) + ":" + args.get(i);
			if ((++i) < argNames.size()) {
				argsTyped += ", ";
				arguments += ", ";
			}
		}
		className = getASClassName();
		if (method.getAnnotation(RemoveEntities.class) != null) {
			writeRemoveEntitiesFunction = true;
		} else {
			writeRemoveEntitiesFunction = false;
		}
		if (method.getAnnotation(AddEntities.class) != null) {
			writeAddEntitiesFunction = true;
		} else {
			writeAddEntitiesFunction = false;
		}
	}

	@Override
	protected String getASPackage() {
		String packageName = this.method.getDeclaringClass().getPackage().getName();
		String classNameLoc = this.method.getDeclaringClass().getSimpleName();
		return packageName + "." + classNameLoc.substring(0, 1).toLowerCase() + classNameLoc.substring(1);
	}

	@Override
	protected void checkClass() throws IllegalClassException, IgnoredClassException {
		return;
	}

	@Override
	protected boolean isInterface() {
		return false;
	}

	@Override
	protected String getASClassName() {
		return method.getName();
	}

	@Override
	protected String getASClassComment() throws NoCommentClassException {
		if (comment == null) {
			return "";
		}
		return comment;
	}

	@Override
	protected String getASSuperClassName() throws NoSuperClassException {
		return "AbstractServiceResponder";
	}

	@Override
	protected void createSeeAlsoASClass() {
		return;
	}

	@Override
	protected Collection<String> getASInterfacesName() {
		return null;
	}

	@Override
	abstract protected Set<String> getASImports();

	@Override
	protected Collection<String> getASClassAnnotations() throws NoAnnotationClassException {
		throw new NoAnnotationClassException();
	}

	@Override
	protected Map<String, String> getASEvents() {
		// Rajoute les evenement typé au service
		Map<String, String> events = new HashMap<String, String>();
		Class[] exceptions = method.getExceptionTypes();
		for (Class exception : exceptions) {
			events.put("fault" + exception.getSimpleName(), "mx.rpc.events.FaultEvent");
		}
		return events;
	}

	@Override
	protected Set<String> getASNamespaces() {
		Set<String> nss = new HashSet<String>();
		nss.add("mx_internal");
		return nss;
	}

	@Override
	final protected void writeASBodyConstructor(OutputStreamWriter out) throws IOException {
		out.write("\t\t\tsuper(\"" + id + "\", \"" + method.getName() + "\");\n");
		if (method.isAnnotationPresent(ShowBusyCursor.class)) {
			boolean showBusyCursor = method.getAnnotation(ShowBusyCursor.class).value();
			out.write("\t\t\tthis.showBusyCursor = " + showBusyCursor + ";\n");
		} else if (method.getDeclaringClass().isAnnotationPresent(ShowBusyCursor.class)) {
			boolean showBusyCursor = method.getDeclaringClass().getAnnotation(ShowBusyCursor.class).value();
			out.write("\t\t\tthis.showBusyCursor = " + showBusyCursor + ";\n");
		}
		Class<?>[] exceptions = method.getExceptionTypes();
		out.write("\t\t\tthis.addEventListener(FaultEvent.FAULT, function(event:FaultEvent):void {\n");
		for (Class<?> ex : exceptions) {
			out.write("\t\t\t\tif(event.fault.faultCode == \"" + ex.getName() + "\") {\n");
			out.write("\t\t\t\t\tvar evt:FaultEvent = new FaultEvent(\"fault" + ex.getSimpleName() + "\", false, false, event.fault);\n");
			out.write("\t\t\t\t\tevent.target.dispatchEvent(evt);\n");
			out.write("\t\t\t\t\treturn;\n");
			out.write("\t\t\t\t}\n");
		}
		out.write("\t\t\t});\n");
	}

	@Override
	protected abstract void writeASFieldsAndMethods(OutputStreamWriter out) throws IOException;

	abstract protected void writeDelayFunctionEntities(OutputStreamWriter out) throws IOException;

	/**
	 * Ecrit la methode qui définit les actions à faire à la définition des donnée.
	 * set datas(datas:" + returnType + "):void
	 * @param out
	 * @throws IOException
	 */
	protected void writeDatasGetterSetter(OutputStreamWriter out, String collectionOf) throws IOException {
		if (!"void".equals(returnType)) {
			out.write("\t\t/**\n");
			out.write("\t\t * Les données propagées à l'interface\n");
			out.write("\t\t */\n");
			out.write("\t\tprivate var _datas:" + returnType + ";\n");
			out.write("\t\t[Bindable]\n");
			if (collectionOf != null) {
				out.write("\t\t[ArrayElementType(\"" + collectionOf + "\")]\n");
			}
			out.write("\t\tpublic function get datas():" + returnType + " {\n");
			out.write("\t\t\treturn _datas;\n");
			out.write("\t\t}\n");
			out.write("\t\tprotected function set datas(datas:" + returnType + "):void {\n");
			out.write("\t\t\t_datas = datas;\n");
			out.write("\t\t\tvar event:ResultEvent = new ResultEvent(ResultEvent.RESULT, true, true, datas);\n");
			out.write("\t\t\tthis.dispatchEvent(event);\n");
			out.write("\t\t}\n\n");
		}
	}

	/**
	 * Ecrit une methode AS disant si les données en base sont périmées.
	 * isDatasOutOfDateMethod(" + argsTyped + ")
	 * @param out
	 * @throws IOException
	 */
	abstract protected void writeIsDatasOutOfDateBody(OutputStreamWriter out) throws IOException;

	/**
	 * Ecrit la methode qui appel le service distant
	 * send(" + argsTyped + "):void
	 * @param out
	 * @throws IOException
	 */
	abstract protected void writeSendBody(OutputStreamWriter out) throws IOException;

	/**
	 * Ecrit la fonction qui, il y a lieu, permet de lancer les appels "delayed" aux services distants
	 * (utilisé lorsque l'offlinePolicy du service est fixée à DELAY)
	 * @param out
	 * @throws IOException 
	 */
	abstract protected void writeLaunchDelayedServices(OutputStreamWriter out) throws IOException;

	/**
	 * Ecrit la methode qui recoit le resultat du reseau
	 * resultNetworkHandler(event:ResultEvent):void
	 * @param out
	 * @throws IOException
	 */
	abstract protected void writeResultNetworkHandlerBody(OutputStreamWriter out) throws IOException;

	/**
	 * Ecrit la methode qui definit le comportement en fonction du reseau.
	 * updateAvailability(event:StatusEvent):void
	 * @param out
	 * @throws IOException
	 */
	protected void writeUpdateAvailabilityBody(OutputStreamWriter out) throws IOException {
		// Que se passe t'il quand l'état du réseau change
		//out.write("\t\t\tavailable = NetworkMonitor.available;\n");
		ASPattern.writeUpdateAvailabilityBody(out);
	}

	/**
	 * 
	 * @param out
	 * @throws IOException 
	 */
	protected void writeRemoveEntitiesFunctions(OutputStreamWriter out) throws IOException {
		ASRemoveEntitiesPattern.writeRemoveEntitiesFunctions(out, method, argsTyped, annotedNames);
	}

	/**
	 * 
	 */
	protected void writeAddEntitiesFunctions(OutputStreamWriter out) throws IOException {
		ASAddEntitiesPattern.writeAddEntitiesFunctions(out, method, argsTyped, annotedNames);
	}

	/**
	 * Ecrit les méthodes liées à l'annotation @LinkedServices
	 * @throws IOException 
	 */
	protected void writeLinkedServicesFunctions(OutputStreamWriter out) throws IOException {

		LinkedServices linkedServices = method.getAnnotation(LinkedServices.class);
		//il y a une annotation @LinkedServices
		if (linkedServices != null) {
			CanceledService[] canceledServices = linkedServices.canceledServices();
			ChainLoad[] chainLoads = linkedServices.chainLoads();

			if (canceledServices.length > 0) {

				out.write("\t\t///////////////////////////////\n");
				out.write("\t\t// début des fonctions \"cancel\"\n\n");
				writeCancelFunctions(out, canceledServices);
				out.write("\n\t\t// fin des fonctions \"cancel\"\n");
				out.write("\t\t///////////////////////////////\n\n");
			}

			if (chainLoads.length > 0) {
				out.write("\t\t///////////////////////////////\n");
				out.write("\t\t// début des fonctions \"chainLoad\"\n\n");
				writeChainLoadFunctions(out, chainLoads);
				out.write("\n\t\t// fin des fonctions \"chainLoad\"\n");
				out.write("\t\t///////////////////////////////\n\n");
			}
		}
	}

	/**
	 * Ecrit la fonction qui permet de regarder si des services sont en attentes (delayed)
	 * et de les lancer.
	 * @param out
	 * @throws IOException 
	 */
	protected void writeCheckDelayedServices(OutputStreamWriter out) throws IOException{
		ASDelayedFunctionPattern.writeCheckDelayedServices(out);
	}

	private void writeCancelFunctions(OutputStreamWriter out, CanceledService[] canceledServices) throws IOException {

		for (CanceledService canceledService : canceledServices) {

			try {
				String functionName = ASCancelFunctionPattern.writeOneCancelFunction(out, canceledService, annotedNames, annotedClass, argsTyped, method);
				cancelFunctionsNames.add(functionName);
			} catch (IllegalParameterException ex) {
				System.out.println("ECHEC de l'écriture de la fonction cancel de " + canceledService.methodClass() + "." + canceledService.method() + canceledService.toString() + " : ");
				System.out.println("dans " + method.toString());
				System.out.println(ex);
			} catch (MissingAnnotationException ex) {
				System.out.println("ECHEC de l'écriture de la fonction cancel de " + canceledService.methodClass() + "." + canceledService.method() + " : ");
				System.out.println("dans " + method.toString());
				System.out.println(ex);

			}
		}
	}

	private void writeChainLoadFunctions(OutputStreamWriter out, ChainLoad[] chainLoads) throws IOException {
		for (ChainLoad chainLoad : chainLoads) {
			try {
				String functionName = ASChainLoadsPattern.writeChainLoadFunction(out, chainLoad, method, returnType);
				chainLoadFunctionsNames.add(functionName);
			} catch (IllegalParameterException ex) {
				System.out.println("ECHEC de l'écriture de la fonction chainLoad");
				System.out.println(ex);
			}

		}
	}
}
